package cn.ilovese.collect.dto.recommend;

import cn.ilovese.collect.datamapper.report.ReportMapper;
import cn.ilovese.collect.datamapper.report.ScoreMapper;
import cn.ilovese.collect.datamapper.report.TFIDFSimilarityMapper;
import cn.ilovese.collect.datamapper.task.TaskMapper;
import cn.ilovese.collect.datamapper.user.UserMapper;
import cn.ilovese.collect.po.report.Report;
import cn.ilovese.collect.po.report.TFIDFSimilarity;
import cn.ilovese.collect.po.task.Task;
import cn.ilovese.collect.po.user.User;
import com.hankcs.hanlp.HanLP;
import com.hankcs.hanlp.corpus.tag.Nature;
import com.hankcs.hanlp.seg.common.Term;
import javafx.util.Pair;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.*;

@Service
public class WorkerGroupRecommend implements GroupRecommendStrategy {
    private static WorkerGroupRecommend instance;

    @Resource
    UserMapper userMapper;
    @Resource
    ScoreMapper scoreMapper;
    @Resource
    TFIDFSimilarityMapper tfidfSimilarityMapper;
    @Resource
    TaskMapper taskMapper;
    @Resource
    ReportMapper reportMapper;

    //TODO:ly jyg也写了一些工人特性相关的东西，包括PPT给的专业能力、任务偏好、活跃度、测试设备，都可以加入优化目标。
    //TODO:目前是只有两个指标的！先把框架搞出来吧..
    //TODO:添加两个“领域知识”（和任务相关性+工人之间区分性） and 修改这个workerScore用的数据
    private static List<Double> bugDetectChance;//按顺序，每个工人有多大几率能检测出bug
    private static List<Double> workerScore;
    private static List<Integer> workerId;//工人的一个综合打分（以往报告的平均分）啥的
    private static List<Double> workerRelevance;//领域相似度
    private static Map<Integer,List<String>> workerDomains;//所有工人的技术领域
    private static List<String> taskDomain;//本任务的技术领域

    //每一代扩增到100个 再选出50个进入下一代
    private static int groupSizeBefore=100;
    private static int groupSizeAfter=50;

    @Override
    public List<Integer> recommendExecute(Integer taskId) {
        //TODO:小规模的时候！！（只worker总人数就不多）
        System.out.println(userMapper);
        List<User> workers=userMapper.selectByRole("worker");
        if(workers.size()<4){
            List<Integer>res=new ArrayList<>();
            for(int i=0;i<workers.size();i++)
                res.add(workers.get(i).getId());
            return res;
        }
        if(workers.size()<20){
            //数据分治；用动态规划处理工人人数更少的情况。
            //工业环境不会太去考虑这种人数场景，但有几点思考：
            /*
            * 1.遗传算法在人数较少的时候完全是低效且没必要的，精度也会相对低一些
            * 2.网站会有一些众包工人人数较少的场景出现，例如创办初期；或延展业务，包给某小规模团队做内部众包测试
            * 3.为小规模情况
            * */
        }
        workerId=new ArrayList<>();
        bugDetectChance=new ArrayList<>();
        workerScore=new ArrayList<>();
        List<List<Boolean>> workerSets=new ArrayList<>();//遗传算法的染色体；选择工人的集合
        for(User worker:workers){
            workerId.add(worker.getId());
            bugDetectChance.add(0.5);//TODO:计算真正找到bug的概率，用逻辑回归
            //TODO:这几个属性目前都是直接random的
            workerScore.add(Math.random());
            workerRelevance.add(Math.random());
        }
        //初始化种群：固定大小size

        for(int i=0;i<groupSizeBefore;i++){
            List<Boolean>now=new ArrayList<>();
            for(int j=0;j<workers.size();i++){
                //随机初始化 多大概率选择可调整
                double number = Math.random();
                if(number>0.3){
                    now.add(false);
                }
                else{
                    now.add(true);
                }
            }
            workerSets.add(now);
        }

        List<Integer>res=NSGA_GetBestSet(workerSets);
        return res;
    }
    //遗传算法主方法
    List<Integer> NSGA_GetBestSet(List<List<Boolean>> workerSets){
        List<Integer>res=new ArrayList<>();
        //TODO:遗传算法迭代原则：迭代100次？
        int T=100;
        List<List<Boolean>> workerSetsNow=workerSets;
        while(T>0){
            T--;
            workerSetsNow=processNSGA(workerSetsNow);
        }
        Double resNum=Double.MAX_VALUE;
        Integer resID=0;

        //对workerSet进行操作，仅保留其pareto最优解，然后再做最后一步的选择
        workerSetsNow=getParetoFront(workerSetsNow);

        for(int i=0;i<workerSetsNow.size();i++){
            //TODO：找出一个最优解返回！！在pareto前沿（最优解集合）中直接累加所有值 找个最好的！

            Double valNow=calcVal(workerSetsNow.get(i));
            if(valNow<resNum){
                resNum=valNow;
                resID=i;
            }
        }
        for(int i=0;i<workerSetsNow.get(resID).size();i++){
            if(workerSetsNow.get(resID).get(i))
                res.add(workerId.get(i));
        }
        return res;
    }

    //从集合中获取帕累托前沿
    List<List<Boolean>> getParetoFront(List<List<Boolean>> workerSetNow){
        List<Boolean> bk=new ArrayList<>();
        int len=workerSetNow.size();
        for(int i=0;i<len;i++){
            bk.add(true);//true表示没有被支配，属于pareto前沿
            for(int j=0;j<len;j++){
                if(i==j)continue;
                if(ParetoControl(workerSetNow.get(i),workerSetNow.get(j))){
                    //j支配了i
                    bk.set(i,false);
                    break;
                }
            }
        }
        List<List<Boolean>> res=new ArrayList<>();
        for(int i=0;i<len;i++){
            if(bk.get(i))res.add(workerSetNow.get(i));
        }
        return res;
    }
    //2支配了1则返回true
    boolean ParetoControl(List<Boolean> src1,List<Boolean> src2){
        Boolean res=true;
        //TODO:遍历所有条件，看是否支配

        return res;
    }

    Double calcVal(List<Boolean> workerSet){
        //TODO:计算种群贡献 考虑策略：
        Double res=Math.random();
        return res;
    }
    //同样采用拓扑排序
    List<Integer> getFinalOrderLevel(List<Integer> controlGroupId,List<Double> distanceIndex){
        List<Integer>res=new ArrayList<>();
        int len=controlGroupId.size();
        for(int i=0;i<len;i++){
            res.add(-1);//表示没分组
        }
        int cnt=0;//现在有多少被处理了
        //每层用queue维护 第一个数为id 第二个数为层数
        Queue<Pair<Integer,Integer>> queue = new LinkedList();
        //初始化入度
        List<Integer> inIndex=new ArrayList<>();
        List<List<Integer>> edge=new ArrayList<>();
        for(int i=0;i<len;i++){
            inIndex.add(0);
            edge.add(new ArrayList<>());
        }
        for(int i=0;i<len;i++){
            for(int j=0;j<len;j++){
                if(i==j)continue;
                //如果j优于i(支配序和拥挤度综合考虑)
                if((controlGroupId.get(j)<controlGroupId.get(i))||(controlGroupId.get(j)==controlGroupId.get(i)&&distanceIndex.get(j)>distanceIndex.get(i))){
                    int curNum=inIndex.get(i)+1;
                    inIndex.set(i,curNum);
                    List<Integer> edges=edge.get(j);
                    edges.add(i);
                    edge.set(j,edges);
                }
            }
        }
        for(int i=0;i<len;i++){
            if(inIndex.get(i)==0){
                queue.offer(new Pair(i,0));
                res.set(i,0);
            }
        }
        //拓扑排序
        while(!queue.isEmpty()){
            Pair<Integer,Integer> now=queue.poll();
            int id=now.getKey(),level=now.getValue();
            List<Integer> edges=edge.get(id);
            for(int i=0;i<edges.size();i++){
                int to=edges.get(i);
                int num=inIndex.get(to);
                if(num==1){
                    inIndex.set(to,0);
                    queue.offer(new Pair(to,level+1));
                    res.set(to,level+1);
                }
                else{
                    inIndex.set(to,num-1);
                }
            }
        }
        return res;
    }
    //两个父代遗传变异 产生两个子代
    List<List<Boolean>> calcSon(List<Boolean> fa1,List<Boolean> fa2){
        List<List<Boolean>> res=new ArrayList<>();
        List<Boolean> tmpSon0=new ArrayList<>();
        List<Boolean> tmpSon1=new ArrayList<>();
        Random rd = new Random();
        int len=fa1.size();
        //采用三个交叉互换点
        int swapPoint[]=new int[3];
        swapPoint[0]=rd.nextInt(len-1);
        swapPoint[1]=rd.nextInt(len-1);
        while(swapPoint[0]==swapPoint[1])
            swapPoint[1]=rd.nextInt(len-1);
        swapPoint[2]=rd.nextInt(len-1);
        while(swapPoint[0]==swapPoint[2]||swapPoint[1]==swapPoint[2]){
            swapPoint[2]=rd.nextInt(len-1);
        }
        for(int i=0;i<=3;i++){
            int posl=0;//处理区间：posl到posr
            if(i!=0)posl=swapPoint[i-1];
            int posr=len-1;
            if(i!=3)posr=swapPoint[i]-1;
            for(int j=posl;j<=posr;j++){
                if(i%2==0){
                    tmpSon0.add(fa1.get(j));
                    tmpSon1.add(fa2.get(j));
                }
                else{
                    tmpSon0.add(fa2.get(j));
                    tmpSon1.add(fa1.get(j));
                }
            }
        }
        //变异 2/len的概率
        int prob=rd.nextInt(len);
        if(prob<2){
            //变异
            int pos=rd.nextInt(len);
            tmpSon0.set(pos,!tmpSon0.get(pos));
        }
        prob=rd.nextInt(len);
        if(prob<2){
            //变异
            int pos=rd.nextInt(len);
            tmpSon1.set(pos,!tmpSon1.get(pos));
        }
        res.add(tmpSon0);
        res.add(tmpSon1);
        return res;
    }

    //TODO:NSGA遗传算法的迭代过程（一次迭代）
    List<List<Boolean>> processNSGA(List<List<Boolean>> workerSets){
        List<List<Boolean>> res=new ArrayList<>();
        //step1:通过非支配序和拥挤度计算 选择父代
        //1.1非支配排序 得出所在偏序组
        List<Integer> controlGroupId=controlSort(workerSets);
        //1.2拥挤度计算，得出每个点的拥挤度
        List<Double> distanceIndex=getDistanceIndex(workerSets);
        //1.3根据非支配序和拥挤度再次进行偏序排序
        List<Integer> finalOrderLevel=getFinalOrderLevel(controlGroupId,distanceIndex);
        //1.4按照1.3得出的偏序关系选择下一代；如果某组无法全部被选取，以拥挤度更大为选择目标

        List<List<Boolean>> nextGenerationFather=new ArrayList<>();
        int chosen=0;
        //TODO:排序后真正选择最优的50个！
        while (nextGenerationFather.size()<=groupSizeAfter){//选出进入下一代（有繁衍资格的）
            nextGenerationFather.add(workerSets.get(chosen));
            chosen++;
        }
        //step2:随机多次，随机选出两个父代，产生子代（子代规模可调整）
        List<List<Boolean>> nextGenerationSon=new ArrayList<>();
        while (nextGenerationSon.size()<=groupSizeAfter){
            //TODO:fa1和fa2替换成随机数
            Integer fa1=0,fa2=0;
            List<List<Boolean>> sons=calcSon(nextGenerationFather.get(fa1),nextGenerationFather.get(fa2));
            //一次交配产生两个孩子 容量允许时全部加入下一代
            if(nextGenerationSon.size()<groupSizeAfter)
                nextGenerationSon.add(sons.get(0));
            if(nextGenerationSon.size()<groupSizeAfter)
                nextGenerationSon.add(sons.get(1));
        }
        //step3:父代子代合并 返回准备下一次迭代
        for (int i=0;i<nextGenerationFather.size();i++)
            res.add(nextGenerationFather.get(i));
        for(int i=0;i<nextGenerationSon.size();i++)
            res.add(nextGenerationSon.get(i));
        return res;
    }
    //在某特定指标上计算拥挤度
    List<Double> calcDistanceIndex(List<Pair<Integer,Double>> idAndValue){
        List<Double>res=new ArrayList<>();
        int len=idAndValue.size();
        Double div=idAndValue.get(len-1).getValue()-idAndValue.get(0).getValue();
        res.set(idAndValue.get(0).getKey(),Double.MAX_VALUE/(len+1));
        res.set(idAndValue.get(len-1).getKey(),Double.MAX_VALUE/(len+1));
        for(int i=1;i<len+1;i++){
            //计算两头之外的拥挤度
            Double val=idAndValue.get(i+1).getValue()-idAndValue.get(i-1).getValue();
            int index=idAndValue.get(i).getKey();
            res.set(index,val/div);
        }
        return res;
    }

    /**
     * 以下几个方法均为对一个染色体（工人组）在各个方面下计算其函数值
     * 包括组大小，bug检测概率，领域相关，多样性等等
     * @param workerSet
     * @return
     */
    Integer calcWorkerNum(List<Boolean> workerSet){
        Integer res=0;
        for(int i=0;i<workerSet.size();i++){
            if(workerSet.get(i))res++;
        }
        return res;
    }

    Double calcBugDetectProb(List<Boolean> workerSet){
        Double res=Math.random();

        return res;
    }
    Double calcDomainRelevance(List<Boolean> workerSet){
        Double res=Math.random();

        return res;
    }
    Double calcWorkerGeneralScore(List<Boolean> workerSet){
        Double res=Math.random();

        return res;
    }
    Double calcGroupDiversity(List<Boolean> workerSet){
        Double res=Math.random();

        return res;
    }

    //计算拥挤度 根据公式
    List<Double> getDistanceIndex(List<List<Boolean>> workerSets){
        int len=workerSets.size();
        List<Double>res=new ArrayList<>();
        for(int i=0;i<len;i++)
            res.add(0.0);
        //key对应是哪个点（会排序打乱）value对应值 用于传参
        List<Pair<Integer,Double>> idAndValue=new ArrayList<>();

        //TODO:排序！！！因为前四个指标都是最大化任务，把第五个指标也变成最大化，然后直接把优化目标改成最大化
        //指标1：bug detect chance（TODO：用ly jyg zzj算出来的指标+报告评分逻辑回归，注意不要和3重复）
        for(int i=0;i<len;i++){
            int tot=workerSets.get(i).size();
            Double arg=1.0*tot-1.0*calcWorkerNum(workerSets.get(i));
            idAndValue.set(i,new Pair(i,arg));
        }
        Collections.sort(idAndValue,(a,b)-> (int) (a.getValue()-b.getValue()));
        List<Double> tmp=calcDistanceIndex(idAndValue);
        for(int i=0;i<tmp.size();i++)
            res.set(i,res.get(i)+tmp.get(i));
        //指标2：工人和任务最大化相关性
        for(int i=0;i<len;i++){
            int tot=workerSets.get(i).size();
            Double arg=1.0*tot-1.0*calcWorkerNum(workerSets.get(i));
            idAndValue.set(i,new Pair(i,arg));
        }
        Collections.sort(idAndValue,(a,b)-> (int) (a.getValue()-b.getValue()));
        tmp=calcDistanceIndex(idAndValue);
        for(int i=0;i<tmp.size();i++)
            res.set(i,res.get(i)+tmp.get(i));
        //指标3：工人评分最大化（TODO：用ly jyg zzj算出来的指标+报告评分综合考量）
        for(int i=0;i<len;i++){
            int tot=workerSets.get(i).size();
            Double arg=1.0*tot-1.0*calcWorkerNum(workerSets.get(i));
            idAndValue.set(i,new Pair(i,arg));
        }
        Collections.sort(idAndValue,(a,b)-> (int) (a.getValue()-b.getValue()));
        tmp=calcDistanceIndex(idAndValue);
        for(int i=0;i<tmp.size();i++)
            res.set(i,res.get(i)+tmp.get(i));
        //指标4：工人差异最大化（文本分析）
        for(int i=0;i<len;i++){
            int tot=workerSets.get(i).size();
            Double arg=1.0*tot-1.0*calcWorkerNum(workerSets.get(i));
            idAndValue.set(i,new Pair(i,arg));
        }
        Collections.sort(idAndValue,(a,b)-> (int) (a.getValue()-b.getValue()));
        tmp=calcDistanceIndex(idAndValue);
        for(int i=0;i<tmp.size();i++)
            res.set(i,res.get(i)+tmp.get(i));
        //指标5：工人人数最小化->未选择工人人数最大化
        for(int i=0;i<len;i++){
            int tot=workerSets.get(i).size();
            Double arg=1.0*tot-1.0*calcWorkerNum(workerSets.get(i));
            idAndValue.set(i,new Pair(i,arg));
        }
        Collections.sort(idAndValue,(a,b)-> (int) (a.getValue()-b.getValue()));
        tmp=calcDistanceIndex(idAndValue);
        for(int i=0;i<tmp.size();i++)
            res.set(i,res.get(i)+tmp.get(i));
        return res;
    }

    //非支配排序 采用拓扑排序实现
    List<Integer> controlSort(List<List<Boolean>> workerSets){
        List<Integer>res=new ArrayList<>();
        int len=workerSets.size();
        for(int i=0;i<len;i++){
            res.add(-1);//表示没分组
        }
        int cnt=0;//现在有多少被处理了
        //每层用queue维护 第一个数为id 第二个数为层数
        Queue<Pair<Integer,Integer>> queue = new LinkedList();
        //初始化入度
        List<Integer> inIndex=new ArrayList<>();
        List<List<Integer>> edge=new ArrayList<>();
        for(int i=0;i<len;i++){
            inIndex.add(0);
            edge.add(new ArrayList<>());
        }
        for(int i=0;i<len;i++){
            for(int j=0;j<len;j++){
                if(i==j)continue;
                if(ParetoControl(workerSets.get(i),workerSets.get(j))){
                    int curNum=inIndex.get(i)+1;
                    inIndex.set(i,curNum);
                    List<Integer> edges=edge.get(j);
                    edges.add(i);
                    edge.set(j,edges);
                }
            }
        }
        for(int i=0;i<len;i++){
            if(inIndex.get(i)==0){
                queue.offer(new Pair(i,0));
                res.set(i,0);
            }
        }
        //拓扑排序
        while(!queue.isEmpty()){
            Pair<Integer,Integer> now=queue.poll();
            int id=now.getKey(),level=now.getValue();
            List<Integer> edges=edge.get(id);
            for(int i=0;i<edges.size();i++){
                int to=edges.get(i);
                int num=inIndex.get(to);
                if(num==1){
                    inIndex.set(to,0);
                    queue.offer(new Pair(to,level+1));
                    res.set(to,level+1);
                }
                else{
                    inIndex.set(to,num-1);
                }
            }
        }
        return res;
    }

    public static WorkerGroupRecommend getInstance(){
        if(instance==null){
            instance=new WorkerGroupRecommend();
        }
        return instance;
    }


    //TODO:逻辑回归
    double sigmoid(double src) {
        return 1.0 / ( 1 + Math.exp(-src));
    }

    //从工人已提交报告中获取其领域知识
    List<String> getWorkerReportDomainKnowledge(Integer userId){
        List<Report> reports=reportMapper.selectByUser(userId);
        List<String> res=new ArrayList<>();
        List<Integer> allDf=new ArrayList<>();
        List<Pair<String,Integer>> wordAndDf=new ArrayList<>();
        String src="";
        for(Report report:reports){
            src+=report.getDescription();
            src+=report.getSteps();
        }
        List<Term> words= HanLP.segment(src);
        List<Task> tasks=taskMapper.selectAll();
        HashMap<String,Boolean> mp=new HashMap<>();
        for(Term tm:words){
            if(!tm.nature.equals(Nature.v)&&!tm.nature.equals(Nature.ad)&&!tm.nature.equals(Nature.a)&&!tm.nature.equals(Nature.n)){
                continue;
            }
            String word=tm.word;
            if(mp.get(word))continue;
            mp.put(word,true);
            int df=0;
            for(Task task:tasks){
                TFIDFSimilarity tfidfSimilarity=tfidfSimilarityMapper.selectByWordAndTask(word,task.getId());
                df+=tfidfSimilarity.getFrequency();
            }
            wordAndDf.add(new Pair(word,df));
            allDf.add(df);
        }
        //过滤df太低太高的部分;
        Collections.sort(wordAndDf,(a,b)->a.getValue()-b.getValue());
        int ignorel= (int) Math.floor(allDf.size()*0.05);
        int ignorer= (int) Math.floor(allDf.size()*0.95);
        for(int i=ignorel;i<ignorer;i++)
            res.add(wordAndDf.get(i).getKey());
        return res;
    }

    //从工人已提交报告中获取其领域知识
    List<String> getTaskDomainKnowledge(Integer taskId){
        Task task=taskMapper.selectByPrimaryKey(taskId);
        List<String> domainWords=new ArrayList<>();
        String src=task.getTaskName()+task.getDescription()+task.getEnvironment();

        List<Term> words= HanLP.segment(src);
        HashMap<String,Boolean> mp=new HashMap<>();
        for(Term tm:words){
            if(!tm.nature.equals(Nature.v)&&!tm.nature.equals(Nature.ad)&&!tm.nature.equals(Nature.a)&&!tm.nature.equals(Nature.n)){
                continue;
            }
            String word=tm.word;
            if(mp.get(word))continue;
            mp.put(word,true);
            domainWords.add(word);
        }
        return domainWords;
    }
}
